package drds.binlog.parse.driver.socket;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.channels.ClosedByInterruptException;

/**
 * 使用BIO进行dump
 */
public class BioSocketChannel implements SocketChannel
{

    static final int DEFAULT_CONNECT_TIMEOUT = 10 * 1000;
    static final int SO_TIMEOUT = 1000;
    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;

    BioSocketChannel(Socket socket) throws IOException
    {
        this.socket = socket;
        this.inputStream = new BufferedInputStream(socket.getInputStream(), 16384);
        this.outputStream = socket.getOutputStream();
    }

    //
    public SocketAddress getLocalSocketAddress()
    {
        Socket socket = this.socket;
        if (socket != null)
        {
            return socket.getLocalSocketAddress();
        }

        return null;
    }

    public SocketAddress getRemoteSocketAddress()
    {
        Socket socket = this.socket;
        if (socket != null)
        {
            return socket.getRemoteSocketAddress();
        }

        return null;
    }

    //
    public boolean isConnected()
    {
        Socket socket = this.socket;
        if (socket != null)
        {
            return socket.isConnected();
        }
        return false;
    }

    //
    public void write(byte[]... bytes) throws IOException
    {
        OutputStream outputStream = this.outputStream;
        if (outputStream != null)
        {
            for (byte[] $bytes : bytes)
            {
                outputStream.write($bytes);
            }
        } else
        {
            throw new SocketException("Socket already closed.");
        }
    }

    public byte[] read(int needReadSize) throws IOException
    {
        InputStream inputStream = this.inputStream;
        byte[] bytes = new byte[needReadSize];
        int remainSize = needReadSize;
        if (inputStream == null)
        {
            throw new SocketException("Socket already closed.");
        }
        while (remainSize > 0)
        {
            try
            {
                int haveReadSize = inputStream.read(bytes, needReadSize - remainSize, remainSize);
                if (haveReadSize > -1)
                {
                    remainSize -= haveReadSize;
                } else
                {
                    throw new IOException("EOF encountered.");
                }
            } catch (SocketTimeoutException te)
            {
                if (Thread.interrupted())
                {
                    throw new ClosedByInterruptException();
                }
            }
        }
        return bytes;
    }

    public byte[] read(int needReadSize, int timeout) throws IOException
    {
        InputStream inputStream = this.inputStream;
        byte[] bytes = new byte[needReadSize];
        int remainSize = needReadSize;
        int accTimeout = 0;
        if (inputStream == null)
        {
            throw new SocketException("Socket already closed.");
        }
        while (remainSize > 0 && accTimeout < timeout)
        {
            try
            {
                int haveReadSize = inputStream.read(bytes, needReadSize - remainSize, remainSize);
                if (haveReadSize > -1)
                {
                    remainSize -= haveReadSize;
                } else
                {
                    throw new IOException("EOF encountered.");
                }
            } catch (SocketTimeoutException te)
            {
                if (Thread.interrupted())
                {
                    throw new ClosedByInterruptException();
                }
                accTimeout += SO_TIMEOUT;
            }
        }
        if (remainSize > 0 && accTimeout >= timeout)
        {
            throw new SocketTimeoutException("Timeout occurred, failed to read " + needReadSize + " bytes in " + timeout
                    + " milliseconds.");
        }
        return bytes;
    }

    @Override
    public void read(byte[] bytes, int offset, int length, int timeout) throws IOException
    {
        InputStream inputStream = this.inputStream;
        int accTimeout = 0;
        if (inputStream == null)
        {
            throw new SocketException("Socket already closed.");
        }

        int allHaveReadSize = 0;
        while (allHaveReadSize < length && accTimeout < timeout)
        {
            try
            {
                int haveReadSize = inputStream.read(bytes, offset + allHaveReadSize, length - allHaveReadSize);
                if (haveReadSize > -1)
                {
                    allHaveReadSize += haveReadSize;
                } else
                {
                    throw new IOException("EOF encountered.");
                }
            } catch (SocketTimeoutException te)
            {
                if (Thread.interrupted())
                {
                    throw new ClosedByInterruptException();
                }
                accTimeout += SO_TIMEOUT;
            }
        }

        if (allHaveReadSize < length && accTimeout >= timeout)
        {
            throw new SocketTimeoutException("Timeout occurred, failed to read " + length + " bytes in " + timeout
                    + " milliseconds.");
        }
    }

    //
    public void close()
    {
        Socket socket = this.socket;
        if (socket != null)
        {
            try
            {
                socket.shutdownInput();
            } catch (IOException e)
            {
                // Ignore, could not do anymore
            }
            try
            {
                socket.shutdownOutput();
            } catch (IOException e)
            {
                // Ignore, could not do anymore
            }
            try
            {
                socket.close();
            } catch (IOException e)
            {
                // Ignore, could not do anymore
            }
        }
        this.inputStream = null;
        this.outputStream = null;
        this.socket = null;
    }

}
